package com.googlecode.animator.model
{
	import com.googlecode.animator.data.Animator3DPlayerVO;
	import com.googlecode.animator.data.Notifications;
	import com.googlecode.animator.data.cameras.ActualViewCameraVO;
	import com.googlecode.animator.data.cameras.CameraVO;
	import com.googlecode.animator.data.markers.GlobalMarkerVO;
	import com.googlecode.animator.data.markers.HideMarkerVO;
	import com.googlecode.animator.data.markers.KeyframeVO;
	import com.googlecode.animator.data.objects3d.Object3DVO;
	import com.googlecode.caurina.transitions.Tweener;
	
	import flash.events.Event;
	import flash.utils.Dictionary;
	
	import mx.collections.ArrayCollection;
	import mx.collections.Sort;
	import mx.collections.SortField;
	
	import org.papervision3d.core.math.Number3D;
	import org.papervision3d.render.BasicRenderEngine;
	import org.papervision3d.scenes.Scene3D;
	import org.puremvc.as3.multicore.patterns.proxy.Proxy;
		
	/**
	 * Stores project settings.
	 * 
	 * Use 'settings' property instead of getData() for a typed reference to the data stored by the proxy.
	 * 
	 * @author Martin
	 * 
	 */
	public class Animator3DPlayerProxy extends Proxy
	{
		/**
		 * PureMVC identifier 
		 */		
		public static const NAME:String = "WebPlayerProxy";
		
		/**
		 * Constructor 
		 * @param proxyName
		 * @param data
		 */
		public function Animator3DPlayerProxy()
		{
			super(NAME, new Animator3DPlayerVO());
		}
		
		/**
		 * Adds the scene and the renderer to the model. 
		 * 
		 */
		public function addScene():void
		{
			settings.scene = new Scene3D();
			settings.renderer = new BasicRenderEngine();

			sendNotification(Notifications.SCENE_ADDED);
		}		
		
		/**
		 * Returns the scene. 
		 * @return 
		 * 
		 */
		public function get scene():Scene3D
		{
			return settings.scene;
		}
		
		/**
		 * Returns the renderer. 
		 * @return 
		 * 
		 */
		public function get renderer():BasicRenderEngine
		{
			return settings.renderer;
		}
		
		/**
		 * Add global marker to model. 
		 * @param gm
		 * 
		 */
		public function addGlobalMarker(gm:GlobalMarkerVO):void
		{
			settings.globalMarkers[gm.id] = gm.clone();
			
			// record the largest time value
			if(gm.time > settings.maxTime)
			{
				settings.maxTime = gm.time;
			}
		}
		
		/**
		 * To simplify, all the globalMarkers are made available. It is unlikely 
		 * they would be needed individually. 
		 * @return 
		 * 
		 */
		public function get globalMarkers():Dictionary
		{
			return settings.globalMarkers;
		}
		
		/**
		 * Add camera to model. 
		 * @param camera
		 * 
		 */
		public function addCamera(camera:CameraVO):void
		{
			settings.cameras[camera.name] = camera.clone();
			
			sendNotification(Notifications.CAMERA_ADDED, camera.clone());
			
		}
		
		/**
		 * Returns the main camera, which can only be of the type ActualViewCamera. 
		 * @return 
		 * 
		 */
		public function get mainCamera():ActualViewCameraVO
		{
			var cam:CameraVO;
			var clone:ActualViewCameraVO;
			
			for each(cam in settings.cameras)
			{
				if(cam is ActualViewCameraVO)
				{
					clone = cam.clone() as ActualViewCameraVO;
					break;
				}
			}
			
			return clone;
		}
		/**
		 * Return the specified camera. 
		 * @param name
		 * @return 
		 * 
		 */
		public function getCamera(name:String):CameraVO
		{
			return settings.cameras[name].clone() as CameraVO;
		}
		
		/**
		 * Returns the specified object3D. 
		 * @param object3D
		 * 
		 */
		public function addObject3D(object3D:Object3DVO):void
		{
			settings.objects3D[object3D.name] = object3D.clone();
			
			// record the largest time value
			var hm:HideMarkerVO;
			for each(hm in object3D.hideMarkers)
			{
				if(hm.end > settings.maxTime)
				{
					settings.maxTime = hm.end;
				}
			}
			
			sendNotification(Notifications.OBJECT3D_ADDED, object3D.clone());
		}
		
		/**
		 * Returns all the objects3D in a dictionary. 
		 * @return 
		 * 
		 */
		public function getObjects3D():Dictionary
		{
			return settings.objects3D;
		}
		
		/**
		 * Returns the specified object3D. 
		 * @param name
		 * @return 
		 * 
		 */
		public function getObject3D(name:String):Object3DVO
		{
			return settings.objects3D[name].clone();
		}
		
		/**
		 * Adds a tween to the tweens array of a specified object3D. 
		 * @param name
		 * @param tween
		 * 
		 */
		public function addTween(name:String, tween:Number3D):void
		{
			((settings.objects3D[name] as Object3DVO).tweens as Array).push(tween.clone());
		}
		
		/**
		 * Remove all tweens. 
		 * @param name
		 * 
		 */
		public function removeAllTweens(name:String):void
		{	
			var tweens:Array = ((settings.objects3D[name] as Object3DVO).tweens as Array);
			
			for(var i:uint=0; i<tweens.length; i++)
			{
				tweens.splice(0, 1);
				i--;
			}
		}
		
		/**
		 * Return thr maximum number of frames needed to hold all action in the animation. 
		 * @return 
		 * 
		 */
		public function get maxTime():uint
		{
			return settings.maxTime;
		}
		/////////////////////////////////////////////////////////
		//
		// Utils
		//
		/////////////////////////////////////////////////////////
		
		public function calculateTweens(name:String):void
		{
			var VO:Object3DVO = getObject3D(name);
			var keyframes:Dictionary = VO.keyframes;

			var keyframesAC:ArrayCollection = new ArrayCollection();
			
			for each(var kf:KeyframeVO in keyframes)
			{
				keyframesAC.addItem(kf);
			}
			
			//Sort keyFrames time ascending
			var s:Sort = new Sort();
			var sF:SortField = new SortField("time", true);
			sF.numeric = true;				
			s.fields = [sF];
			keyframesAC.sort = s;
			keyframesAC.refresh();
			
			//Tween
			if(keyframesAC.length < 1)
			{
				return;
			}
			
			var evt:Event = new Event("event");
			var startPos:Number3D = ((keyframesAC[0] as KeyframeVO).position).clone();
			
			removeAllTweens(VO.name);
			
			addTween(VO.name, startPos);
			
			for(var i:uint=0; i<keyframesAC.length-1; i++)
			{
				var currentPos:Number3D = (keyframesAC[i].position as Number3D).clone();
				var nextPos:Number3D = (keyframesAC[i+1].position as Number3D).clone();
				var t:uint = (keyframesAC[i+1].time - keyframesAC[i].time);

				if(t==0)
				{
					return;
				}
				
				// record the largest time value
				if(t > settings.maxTime)
				{
					settings.maxTime = t;
				}
				
				Tweener.addTween(currentPos, {x:nextPos.x, y:nextPos.y, z:nextPos.z, time:t, useFrames:true, transition:"linear"});
				
				for(var j:uint=0; j<t; j++)
				{
					Tweener.onEnterFrame(evt);
					
					addTween(VO.name, currentPos.clone());
					
				}
				
			}
			
			Tweener.removeTweens(currentPos);
		}
		
		/**
		 * Returns typed reference to the value object. 
		 * @return 
		 * 
		 */
		protected function get settings():Animator3DPlayerVO
		{
			return getData() as Animator3DPlayerVO;
		}

				
	}
}